﻿///////////////////////////////////////////////////////////////////////////////
//
// This file is part of the BassDrop project
//   https://code.google.com/p/bass-drop/
//
//
// Creator:	Gergely Krajcsovszki (krajcsovszkig@hotmail.com)
// Date:	2013-10-26 19:30
//
//
// Copyright © 2013-2014. Gergely Krajcsovszki
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using BassDrop.Core.Common;
using BassDrop.Core.Common.Data.IO;
using BassDrop.Core.Common.Media;
using BassDrop.Core.Common.Media.Playlists;
using BassDrop.DesktopClient.Properties;
using BassDrop.DesktopClient.Utilities;

namespace BassDrop.DesktopClient.UI.Panels
{
	/// <summary>
	/// Panel for displaying playlists
	/// </summary>
	public partial class PlaylistPanel : UserControl, INotifyPropertyChanged
	{
		#region Fields and properties

		/// <summary>
		/// The ID of the playlist update transaction if there is one now
		/// </summary>
		private int? m_transactionId = null;

		private bool m_isReadonly = false;

		/// <summary>
		/// the displayed playlist cannot be edited through this panel (default: false)
		/// </summary>
		public bool Readonly
		{
			get
			{
				return m_isReadonly;
			}
			set
			{
				if ( value == m_isReadonly )
				{
					return;
				}

				m_isReadonly = value;

				UpdateControls();
			}
		}

		private bool m_ordered = false;

		/// <summary>
		/// the displayed playlist has a fixed order, in which it will be played (default: false)
		/// </summary>
		public bool Ordered
		{
			get
			{
				return m_ordered;
			}
			set
			{
				if ( value == m_ordered )
				{
					return;
				}

				m_ordered = value;

				UpdateControls();
			}
		}

		private bool m_canPlay = false;

		/// <summary>
		/// the playlist can be played right away, no need to sync it into the NowPlaying (default: false)
		/// </summary>
		public bool CanPlay
		{
			get
			{
				return m_canPlay;
			}
			set
			{
				if ( value == m_canPlay )
				{
					return;
				}

				m_canPlay = value;
			}
		}

		/// <summary>
		/// The playlist shown on this panel
		/// </summary>
		private Playlist playlist;

		/// <summary>
		/// The playlist shown on this panel
		/// </summary>
		public Playlist Playlist
		{
			get
			{
				return playlist;
			}
			set
			{
				if ( value == playlist )
				{
					return;
				}

				playlist = value;

				BindPlaylist();
			}
		}

		private static bool s_expandDetailsOnSelect = true;

		/// <summary>
		/// Should the collapsed row details be expanded when they are selected
		/// </summary>
		public static bool ExpandDetailsOnSelect
		{
			get
			{
				return s_expandDetailsOnSelect;
			}
			set
			{
				if ( s_expandDetailsOnSelect != value )
				{
					s_expandDetailsOnSelect = value;

					OnStaticPropertyChanged( "ExpandDetailsOnSelect" );
				}
			}
		}

		private bool m_expandDetailsAlways = Settings.Default.PlaylistPanel_ExpandDetailsAlways;

		/// <summary>
		/// Always expand the row details in the datagrid
		/// </summary>
		public bool ExpandDetailsAlways
		{
			get
			{
				return m_expandDetailsAlways;
			}
			set
			{
				if ( m_expandDetailsAlways != value )
				{
					m_expandDetailsAlways = value;
					Settings.Default.PlaylistPanel_ExpandDetailsAlways = value;
					UpdateRowDetailsVisibilityMode();

					OnPropertyChanged( "ExpandDetailsAlways" );
				}
			}
		}

		/// <summary>
		/// The preferred visibility of row details
		/// </summary>
		private DataGridRowDetailsVisibilityMode PreferredRowDetailsVisiblityMode
		{
			get
			{
				return ExpandDetailsAlways
					? DataGridRowDetailsVisibilityMode.Visible
					: (ExpandDetailsOnSelect
						? DataGridRowDetailsVisibilityMode.VisibleWhenSelected
						: DataGridRowDetailsVisibilityMode.Collapsed);
			}
		}

		/// <summary>
		/// Don't let row details visibility to change - if the preferred mode is "VisibleWhenSelected", set
		/// the actual visibility mode to Collapsed
		/// </summary>
		private bool FreezeRowDetailsVisibility
		{
			get
			{
				return IsDragging || playlistTable.SelectedItems.Count > 1 || m_clickedEvenTimes;
			}
		}

		#endregion Fields and properties

		#region Events

		/// <summary>
		/// Reports static property changes
		/// </summary>
		public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;

		/// <summary>
		/// Fires a new StaticPropertyChanged event
		/// </summary>
		/// <param name="propertyName"></param>
		private static void OnStaticPropertyChanged( string propertyName )
		{
			if ( StaticPropertyChanged != null )
			{
				StaticPropertyChanged( null, new PropertyChangedEventArgs( propertyName ) );
			}
		}

		/// <summary>
		/// Event fired when the value of a property changes
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged;

		/// <summary>
		/// Fire a new PropertyChanged event
		/// </summary>
		/// <param name="propertyName">the name of the property that has changed</param>
		private void OnPropertyChanged( string propertyName )
		{
			if ( PropertyChanged != null )
			{
				PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
			}
		}

		#endregion Events

		/// <summary>
		/// Creates a new PlaylistPanel
		/// </summary>
		public PlaylistPanel()
		{
			InitializeComponent();

			UpdateControls();
			UpdateRowDetailsVisibilityMode();

			StaticPropertyChanged += PlaylistPanel_StaticPropertyChanged;
		}

		/// <summary>
		/// Plays the currently selected item of the playlist if there is one
		/// </summary>
		public void PlaySelectedItem()
		{
			if ( playlistTable.SelectedIndex >= 0 )
			{
				var playlist = Playlist;

				if ( !CanPlay )
				{
					Controller.Instance.NowPlaying.SyncFrom( playlist );
					playlist = Controller.Instance.NowPlaying;
				}

				playlist.SelectedIndex = playlistTable.SelectedIndex;
				playlist.Play();
			}
		}

		/// <summary>
		/// Plays the playlist from the first item
		/// </summary>
		public void PlayFromStart()
		{
			var playlist = Playlist;

			if ( !CanPlay )
			{
				Controller.Instance.NowPlaying.SyncFrom( playlist );
				playlist = Controller.Instance.NowPlaying;
			}

			playlist.SelectedIndex = 0;
			playlist.Play();
		}

		/// <summary>
		/// Updates the visibility and enabledness of the controls
		/// </summary>
		private void UpdateControls()
		{
			// TODO: BassDrop.DesktopClient.UI.Panels.PlaylistPanel.UpdateControls()
			if ( Ordered )
			{
				showOriginalOrderButton.Visibility = Visibility.Visible;
				saveOrderButton.Visibility = Visibility.Visible;

				//showOriginalOrderButton.IsEnabled = OrderChanged;
				//saveOrderButton.IsEnabled = OrderChanged && !Readonly;
			}
			else
			{
				showOriginalOrderButton.Visibility = Visibility.Collapsed;
				saveOrderButton.Visibility = Visibility.Collapsed;
			}
		}

		/// <summary>
		/// Binds the value of the Playlist property to the controls
		/// </summary>
		private void BindPlaylist()
		{
			playlistTable.ItemsSource = Playlist;

			// TODO: columns, layout, style (alternating background + separate for different types of selections), sorting
			// Rendezés miért nem megy custom converteres és Uri-t tartalmazó oszlopra?
			// Kézi (húzogatós) rendezés automata mentése?!
		}

		/// <summary>
		/// Adds the passed media file or all the media files under the passed folder to the playlist.
		/// If the passed file is not playable, does nothing
		/// </summary>
		/// <param name="file"></param>
		private void TryAddTracks( string file )
		{
			if ( (File.Exists( file ) && Controller.Instance.PluginManager.CanPlay( new FileInfo( file ).Extension )) ) // TODO: akkor is, ha létezik hozzá plugin?
			{
				Playlist.Add( MediaIO.GetOrCreatePlaylistItemFor( MediaIO.GetOrCreateResourceLocationForUri( new Uri( file, UriKind.Absolute ) ) ) );
			}
			else if ( Directory.Exists( file ) )
			{
				foreach ( var item in Directory.GetFileSystemEntries( file ) )
				{
					TryAddTracks( item );
				}
			}
		}

		/// <summary>
		/// Update the visibility of the row details
		/// </summary>
		private void UpdateRowDetailsVisibilityMode()
		{
			if ( FreezeRowDetailsVisibility && PreferredRowDetailsVisiblityMode == DataGridRowDetailsVisibilityMode.VisibleWhenSelected )
			{
				playlistTable.RowDetailsVisibilityMode = DataGridRowDetailsVisibilityMode.Collapsed;
			}
			else
			{
				playlistTable.RowDetailsVisibilityMode = PreferredRowDetailsVisiblityMode;
			}
		}

		/// <summary>
		/// Updates the row header of the passed row
		/// </summary>
		/// <param name="row"></param>
		private void UpdateRowHeader( DataGridRow row )
		{
			if ( row == null )
			{
				return; // ItemContainerGenerator.ContainerFromItem sometimes returns null, but those rows don't need to be updated anyway
			}

			row.Header = (row.GetIndex() + 1).ToString();
		}

		/// <summary>
		/// Updates all row headers
		/// </summary>
		private void UpdateAllRowHeaders()
		{
			lock ( ((ICollection) Playlist).SyncRoot )
			{
				foreach ( var item in Playlist )
				{
					UpdateRowHeader( (DataGridRow) playlistTable.ItemContainerGenerator.ContainerFromItem( item ) );
				}
			}
		}

		private async void PlaylistPanel_DragEnter( object sender, DragEventArgs e )
		{
			Exception error = null;
			try
			{
				if ( !Readonly && e.Data.GetDataPresent( DataFormats.FileDrop ) )
				{
					foreach ( var file in (string[]) e.Data.GetData( DataFormats.FileDrop ) )
					{
						if ( Directory.Exists( file )
							|| (File.Exists( file )
								&& Controller.Instance.PluginManager.CanPlay( new FileInfo( file ).Extension )) ) // TODO: akkor is, ha létezik hozzá plugin?
						{
							e.Effects = DragDropEffects.All;
							return;
						}
					}
				}

				e.Effects = DragDropEffects.None;
			}
			catch ( Exception ex )
			{
				error = ex;
			}

			if ( error != null )
			{
				await error.Prompt();
			}
		}

		private async void PlaylistPanel_Drop( object sender, DragEventArgs e )
		{
			Exception error = null;
			try
			{
				if ( Readonly || e.Handled )
				{
					return;
				}

				if ( e.Data.GetDataPresent( DataFormats.FileDrop ) )
				{
					foreach ( var file in (string[]) e.Data.GetData( DataFormats.FileDrop ) )
					{
						TryAddTracks( file );
					}
				}

				e.Handled = true;
			}
			catch ( Exception ex )
			{
				error = ex;
			}

			if ( error != null )
			{
				await error.Prompt();
			}
		}

		#region Row reordering via drag-drop

		// Based on http://www.hardcodet.net/2009/03/moving-data-grid-rows-using-drag-and-drop

		/// <summary>
		/// State flag which indicates whether the grid is in edit
		/// mode or not.
		/// </summary>
		private bool IsEditing
		{
			get;
			set;
		}

		private bool m_isDragging;

		/// <summary>
		/// Keeps in mind whether the rows are currently being dragged
		/// </summary>
		private bool IsDragging
		{
			get
			{
				return m_isDragging;
			}
			set
			{
				if ( m_isDragging != value )
				{
					m_isDragging = value;
					UpdateRowDetailsVisibilityMode();
				}
			}
		}

		/// <summary>
		/// The item currently being dragged
		/// </summary>
		public PlaylistItemBase DraggedItem
		{
			get;
			set;
		}

		private void playlistTable_BeginningEdit( object sender, DataGridBeginningEditEventArgs e )
		{
			IsEditing = true;
			//in case we are in the middle of a drag/drop operation, cancel it...
			if ( IsDragging )
			{
				ResetDragDrop();
			}
		}

		private void playlistTable_CellEditEnding( object sender, DataGridCellEditEndingEventArgs e )
		{
			IsEditing = false;
		}

		private async void playlistTable_PreviewMouseLeftButtonDown( object sender, MouseButtonEventArgs e )
		{
			Exception error = null;
			try
			{
				//exit if in edit mode
				if ( Readonly || IsEditing || IsDragging )
				{
					return;
				}

				//find the clicked row
				var row = ((UIElement) sender).TryFindFromPoint<DataGridRow>( e.GetPosition( playlistTable ) );
				if ( row == null )
				{
					return;
				}

				//set flag that indicates we're capturing mouse movements
				IsDragging = true;
				DraggedItem = (PlaylistItemBase) row.Item;
				m_transactionId = Playlist.StartTransaction();
			}
			catch ( Exception ex )
			{
				error = ex;
			}

			if ( error != null )
			{
				await error.Prompt();
			}
		}

		private void Grid_MouseLeftButtonUp( object sender, MouseButtonEventArgs e )
		{
			//reset
			ResetDragDrop();
		}

		private async void Grid_MouseMove( object sender, MouseEventArgs e )
		{
			Exception error = null;
			try
			{
				if ( Readonly || !IsDragging || e.LeftButton != MouseButtonState.Pressed )
				{
					ResetDragDrop();
					return;
				}

				Point position = e.GetPosition( playlistTable );
				var row = playlistTable.TryFindFromPoint<DataGridRow>( position );
				if ( row != null )
				{
					//get the target item
					PlaylistItemBase targetItem = (PlaylistItemBase) row.Item;

					if ( targetItem == null || !ReferenceEquals( DraggedItem, targetItem ) )
					{
						int draggedIndex = Playlist.IndexOf( DraggedItem );
						int targetIndex = Playlist.IndexOf( targetItem );
						Playlist.Move( draggedIndex, targetIndex );
						playlistTable.Items.Refresh(); // without this, Weird Things Happen
					}
				}
			}
			catch ( Exception ex )
			{
				error = ex;
			}

			if ( error != null )
			{
				await error.Prompt();
			}
		}

		/// <summary>
		/// Closes the popup and resets the
		/// grid to read-enabled mode.
		/// </summary>
		private void ResetDragDrop()
		{
			IsDragging = false;
			DraggedItem = null;
			playlistTable.IsReadOnly = false;
			if ( m_transactionId != null )
			{
				Playlist.EndTransaction( (int) m_transactionId );
				m_transactionId = null;
			}
		}

		#endregion Row reordering via drag-drop

		private async void playlistTable_LoadingRow( object sender, DataGridRowEventArgs e )
		{
			Exception error = null;
			try
			{
				UpdateRowHeader( e.Row );
			}
			catch ( Exception ex )
			{
				error = ex;
			}

			if ( error != null )
			{
				await error.Prompt();
			}
		}

		private async void playlistTable_PreviewKeyDown( object sender, KeyEventArgs e )
		{
			Exception error = null;
			try
			{
				switch ( e.Key )
				{
					case Key.Delete:
						{
							if ( !Readonly )
							{
								List<PlaylistItemBase> toRemove = new List<PlaylistItemBase>();

								foreach ( var item in playlistTable.SelectedItems )
								{
									var playlistItem = item as PlaylistItemBase;

									if ( playlistItem != null )
									{
										toRemove.Add( playlistItem );
									}
								}

								if ( toRemove.Count > 0 )
								{
									Playlist.RemoveRange( toRemove );
								}
							}

							e.Handled = true;
						}
						break;

					case Key.Enter:
						{
							PlaySelectedItem();

							e.Handled = true;
						}
						break;
				}
			}
			catch ( Exception ex )
			{
				error = ex;
			}

			if ( error != null )
			{
				await error.Prompt();
			}
		}

		private async void playlistTable_MouseDoubleClick( object sender, MouseButtonEventArgs e )
		{
			Exception error = null;
			try
			{
				PlaySelectedItem();
			}
			catch ( Exception ex )
			{
				error = ex;
			}

			if ( error != null )
			{
				await error.Prompt();
			}
		}

		private void playlistTable_RowDetailsVisibilityChanged( object sender, DataGridRowDetailsEventArgs e )
		{
			// TODO: stackpanel feltöltése használható adatokkal
			/*
			 * https://www.google.com/search?q=wpf+get+element+from+datatemplate
			 * http://msdn.microsoft.com/en-us/library/bb613579(v=vs.110).aspx
			 * http://stackoverflow.com/questions/4586106/wpf-how-to-access-control-from-datatemplate
			 */
		}

		private void PlaylistPanel_StaticPropertyChanged( object sender, PropertyChangedEventArgs e )
		{
			if ( e.PropertyName == "ExpandDetailsOnSelect" )
			{
				UpdateRowDetailsVisibilityMode();
			}
		}

		bool m_rowSelectionChanged = false;
		bool m_clickedEvenTimes = false;

		private void playlistTable_SelectionChanged( object sender, SelectionChangedEventArgs e )
		{
			m_rowSelectionChanged = true;
			m_clickedEvenTimes = false;
			UpdateRowDetailsVisibilityMode();
		}

		private void playlistTable_MouseUp( object sender, MouseButtonEventArgs e )
		{
			if ( !m_rowSelectionChanged )
			{
				m_clickedEvenTimes = !m_clickedEvenTimes;
				UpdateRowDetailsVisibilityMode();
			}

			m_rowSelectionChanged = false;
		}
	}

	#region Value converters

	/// <summary>
	/// Gets the display title from a PlaylistItem
	/// </summary>
	public class TrackMetadataConverter_Title : IValueConverter
	{

		#region IValueConverter Members

		public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
		{
			var track = value as PlaylistItemMediaTrack;

			if ( track != null && track.Track.TrackMetadata != null && track.Track.TrackMetadata.Title != null )
			{
				return track.Track.TrackMetadata.Title;
			}
			else if ( track != null )
			{
				return track.Track.FileName;
			}
			else
			{
				return string.Empty; // TODO: action objects
			}
		}

		public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
		{
			throw new NotImplementedException();
		}

		#endregion
	}

	/// <summary>
	/// Gets the display image from a PlaylistItem
	/// </summary>
	public class TrackMetadataConverter_Image : IValueConverter
	{

		#region IValueConverter Members

		public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
		{
			var track = value as PlaylistItemMediaTrack;

			if ( track != null && track.Track.TrackMetadata != null && track.Track.TrackMetadata.AlbumArtLocation != null )
			{
				try
				{
					return new BitmapImage( track.Track.TrackMetadata.AlbumArtLocation );
				}
				catch ( Exception )
				{
					// no action
				}
			}

			if ( track != null )
			{
				return new BitmapImage( new Uri( "pack://application:,,/logo_transparent.png" ) );
			}
			else
			{
				return new BitmapImage( new Uri( "pack://application:,,/logo_transparent.png" ) ); // TODO: action objects
			}
		}

		public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
		{
			throw new NotImplementedException();
		}

		#endregion
	}

	#endregion Value converters
}
